Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow sign-to-contract commitments in schnorrsigs #588

Closed
wants to merge 6 commits into from

Conversation

jonasnick
Copy link
Contributor

@jonasnick jonasnick commented Jan 29, 2019

This PR implements creation and verification of sign-to-contract commitments in the
schnorrsig module. The following snippet for example puts a commitment to data32
into the (Schnorr) signature.

/* Signer */
secp256k1_s2c_commit_context s2c_ctx;
secp256k1_s2c_commit_context_create(ctx, &s2c_ctx, data32);
secp256k1_schnorrsig_sign(sign, &sig, &nonce_is_negated, msg, sk1, NULL, &s2c_ctx);
secp256k1_s2c_commit_get_original_nonce(ctx, &s2c_original_nonce, &s2c_ctx);

/* Verifier */
secp256k1_schnorrsig_verify_s2c_commit(ctx, &sig, data32, &s2c_original_nonce, nonce_is_negated);

This PR is based on #558. The commits in this PR was previously included in #572.
The tests should provide full coverage of the reachable lines.

There is also functionality for doing pay-to-contract style commitments
(ec_commit), but it's not exposed yet. If it will be in the future it would
make sense to add an optional argument with a SHA midstate to allow hashing
arbitrary version prefixes before hashing the public key (which would be useful
in taproot for example).

Sign-to-contract and right now only work with schnorrsig_sign and
nonce_function_bipschnorr. It should be straightforward to add support for
ECDSA too in the future.

One thing to keep in mind is that for some users moving from ecdsa to
schnorrsigs requires reading the documentation carefully. There is undocumented
functionality in nonce_function_rfc6979 where the additional nonce data is
hashed into the secret. If someone uses that and then call schnorrsig_sign with
similar nonce data this results in a segmentation fault because
nonce_function_bipschnorr which is used by default in schnorrsig_sign expects
an s2c_commit_context object as nonce data and writes back to it.

src/secp256k1.c Outdated
@@ -610,6 +610,103 @@ int secp256k1_ec_pubkey_combine(const secp256k1_context* ctx, secp256k1_pubkey *
return 1;
}

/* Compute an ec commitment tweak as hash(pubkey, data). */
int secp256k1_ec_commit_tweak(const secp256k1_context *ctx, unsigned char *tweak32, const secp256k1_pubkey *pubkey, const unsigned char *data, size_t data_size) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be static

@gmaxwell
Copy link
Contributor

gmaxwell commented Feb 2, 2019

If you want to harden against misuse of the nonce function arguments, you could add a constant uint64 as the first member of the s2c_commit_context and test it.

Generally, I'd like to see us do that with all objects that we allow users to pass around: set a magic value when they're constructed, zero it when they're destroyed. Check for it before other accesses... exception being purely internal objects or elements where a caller might plausibly have thousands of them in existence at once (such that the overhead becomes a concern).

nonce_function_rfc6979 where the additional nonce data is
hashed into the secret.

Odd that we didn't document it-- aux data is actually part of RFC6979.

Copy link
Contributor

@real-or-random real-or-random left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, I wonder whether it is a better idea

  • not to use the nonce function for s2c. (For example, this prevents people from using their own nonce function together with s2c.)
  • instead of a s2c context, have a s2c_opening, which contains all opening information (i.e., the original pubnonce and the information whether the nonce was negated. This looks more like a normal commitment API to me then.)


if (data_size == 0) {
/* That's probably not what the caller wanted */
return 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that's not what the caller wanted, maybe a CHECK or a VERIFY_CHECK is better.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VERIFY_CHECK only works in tests. ARG_CHECK crashes the caller's program. Not sure if that would be better given that there doesn't really seem to be precedent for aborting in such cases in libsecp. Also I don't see a good reason for aborting as this condition can be trivially recovered from (by just returning 0).

src/tests.c Outdated
/* Create random data */
{
secp256k1_scalar d;
random_scalar_order_test(&d);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

secp256k1_rand256 instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

secp256k1_gej_add_ge(&pj, &pj, &p);
secp256k1_pubkey_load(ctx, &p, commitment);
secp256k1_ge_neg(&p, &p);
secp256k1_gej_add_ge(&pj, &pj, &p);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's a good idea to implement an gej_cmp or even pubkey_cmp... No idea.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, good idea. Maybe not in this PR.

*
* The exact representation of data inside is implementation defined and not
* guaranteed to be portable between different platforms or versions. It is however
* guaranteed to be 128 bytes in size, and can be safely copied/moved.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one paragraph above, you say that copying is discouraged

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch. I think there's no issue with copying that structure and I think I just copied this sentence from the MuSig session without thinking about it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

sig_tmp = sig;
secp256k1_rand256(&sig_tmp.data[0]);
CHECK(secp256k1_schnorrsig_verify_s2c_commit(ctx, &sig_tmp, data32, &s2c_original_nonce, nonce_is_negated) == 0);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there should be a test that verification fails when nonce_is_negated is wrong

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@jonasnick
Copy link
Contributor Author

I added a commit that adds a magic number to the sign to contract context struct.

…eaks of public keys.

The functionality is not exposed.
…chnorr do sign-to-contract commitments if an s2c context is provided as nonce data
@jonasnick
Copy link
Contributor Author

squashed

ARG_CHECK(s2c_ctx != NULL);
ARG_CHECK(data32 != NULL);

memcpy(s2c_ctx->magic, &s2c_commit_context_magic, sizeof(s2c_ctx->magic));
Copy link
Contributor

@elichai elichai Jul 1, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably it's just me being not familiar enough with C but why copy from a uint64_t to a unsigned char[8] instead of just putting a uint64_t in the struct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was some reason I don't remember to use uint64_t. But I think using a byte array in the struct is arbitrary... probably easier to read if replaced by uint64_t.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another thing, I'm pretty sure memcpy from uint64_t to unsigned char[8] is platform defined(BE/LE)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but not really an issue as the doc clearly states

The exact representation of data inside is implementation defined and not guaranteed to be portable between different platforms or versions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't matter in the end but the code is certainly easier to read if it's the same type twice (no matter what type that is)

if (data != NULL) {
/* Create a sign-to-contract commitment by setting nonce32 <- nonce32 +
* hash(nonce32*G, s2c_ctx->data) */
secp256k1_s2c_commit_context *s2c_ctx = (secp256k1_s2c_commit_context *)data;
Copy link
Contributor

@elichai elichai Jul 1, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should verify the magic number here and fail if not right.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, good catch

unsigned char magic[8];
unsigned char data[32];
unsigned char data_hash[32];
secp256k1_pubkey original_pubnonce;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should the "public nonce" really be represented as pubkey? for me ge or gej would be more readable, but they're probably not exported....
(I got confused while diving into secp256k1_ec_commit_seckey and secp256k1_ec_commit_tweak about the whole public key thing)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ge or gej are not exported. But, hm, perhaps this should be a pubnonce type if it already confuses people.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, yes. A public key is a public key, and a nonce is a different thing. (I mean we can't enforce anything in C but maybe it's helpful to use different names for the two public types at least.)

Do we have the same problem in other places too?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a second thought... if we really want to optimize for a simple API, then #589 is the way to go. There the entire problem is eliminated by hiding from the user that a signature contains a "nonce".

Copy link
Contributor

@elichai elichai Jul 2, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@real-or-random real-or-random Jul 2, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fair

@jonasnick
Copy link
Contributor Author

jonasnick commented Jul 3, 2019

Closing in favor of #589. Thanks for the feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants